<?php

namespace Sharks\Core\Persistence;

use Closure;
use InvalidArgumentException;
use ReflectionClass;
use Sharks\Core\AppContext;
use Sharks\Core\Persistence\QueryBuilder\Query;
use Sharks\Core\Support\Bag\ParameterBag;
use Sharks\Core\Support\Base\Argument;
use Sharks\Core\Support\Base\Arr;
use Sharks\Core\Support\Base\Precondition;
use Sharks\Core\Exception\ModelException;
use Sharks\Core\Exception\ModelNotFoundException;

/**
 * 
 * 映射模型类
 * 各类数据库操作方法，
 * 多数据库支持
 *     通过Model::on($dbName)
 *
 * 事务支持
 * 
 */
class Model extends Db {

    /** @var UPDATE_AT 更新时间列名 */
    const UPDATED_AT = 'updatedAt';
    /** @var CREATE_AT 创建时间列名 */
    const CREATE_AT = 'createdAt';

    /** @var DEFAULT_ID 默认数据库标识名称 */
    const DEFAULT_ID = array('id');

    protected static $identifier;

    /**
     * 更改Model类各项操作时的数据库连接
     * <pre>
     *     Model::on('other');
     *     $m = SysUser::get(1);
     *     $m->loginname = 'root';
     *     $m->save();
     *     Model::on();
     * </pre>
     * @param  [type] $identifier [description]
     */
    public static function on($identifier = null) {
        self::$identifier = $identifier;
    }

    /**
     * 数据库操作：查询-依据指定标识id获取数据，如果找到相关数据则返回Model实例，否则返回false
     * @param  mixed $id 数据库主键或标识
     * @return Model
     */
    public static function getOrFail($id) {
        $result = static::get($id);
        if (false === $result || empty($result)) {
            throw new ModelNotFoundException("没有查询到标识为 $id 的资源");
        }
        return $result;
    }

    /**
     * 数据库操作：查询-依据指定标识id获取数据，如果找到相关数据则返回Model实例，否则返回false
     * @param  mixed $id 数据库主键或标识
     * @return Model
     */
    public static function get($id) {
        $query = Query::createSelect(self::$identifier)->from(get_called_class());
        if (is_array($id)) {
            foreach ($id as $idName => $i) {
                $query->eq($idName, $i);
            }
        }
        else {
            $query->eq('id', $id);
        }
        return $query->only();
    }



    /**
     * 依据类全称（带命名空间）与已有属性值产生Model实例
     * @param  [type] $class      [description]
     * @param  array  $attributes [description]
     * @return [type]             [description]
     */
    public static function generate($class, array $attributes = array()) {
        $obj = new $class($attributes);
        return $obj;
    }

    /**
     * 依据id标识，删除数据库记录
     * @param  [type] $id [description]
     * @return [type]     [description]
     */
    public static function destroy($id) {
        $query = Query::createDelete(self::$identifier)->from(static::getTable());
        if (is_array($id)) {
            foreach ($id as $idName => $i) {
                $query->eq($idName, $i);
            }
        }
        else {
            $query->eq('id', $id);
        }
        return $query->get();
    }

    /** @var array 属性值 */
    private $attributes;
    /** @var ParameterBag 相关关系 */
    public $relations;

    public function __construct(array $attributes = array()) {
        $this->attributes = $attributes;
        $this->relations = new ParameterBag;
    }

    /**
     * 保存数据至数据库
     * @return mixed|bool
     */
    public function save() {
        if ($this->hasId()) {
            $data = $this->attributes;
            unset($data['id']);

            // 判断是否需要自动设置更新时间.
            if (!isset(static::$at) || !isset(static::$at['NonUpdatedAt'])) {
                $this->attributes[self::UPDATED_AT] = time();
            }

            return Query::createUpdate(self::$identifier)->table(self::getTable())->sets($data)->eq('id', $this->attributes['id'])->get();
        }
        else {
            // 判断是否需要自动设置创建时间
            if (!isset(static::$at) || !isset(static::$at['NonCreatedAt'])) {
                $this->attributes[self::CREATE_AT] = time();
            }

            if (!isset($this->attributes["id"])) {
                $this->attributes["id"] = IdGenerator::uuidStr();
            }

            Query::createInsert()->into(self::getTable())->sets($this->attributes)->get();
            return $this->attributes['id'];
        }
    }

    /**
     * 依据Model Object 自身携带的id标识删除对应数据库数据
     * @return [type] [description]
     */
    public function remove() {
        if ($this->hasId()) {
            $query = Query::createDelete(self::$identifier)->from(self::getTable());
            foreach (self::getIdValues() as $idName => $id) {
                $query->eq($idName, $id);
            }
            return $query->get();
        }
    }

    /**
     * 依据对象自身标识从数据库加载数据
     * @return boolean 是否成功加载
     */
    public function load() {
        if (!$this->hasId()) {
            return false;
        }
        $query = Query::createSelect(self::$identifier)->from(self::getTable());
        foreach (self::getIdValues() as $idName => $id) {
            $query->eq($idName, $id);
        }
        $record = $query->only();
        if (false == $record) {
            return false;
        }
        array_merge($this->attributes, $record->attributes());
        return true;
    }

    /**
     * 是否设置了id
     * @return bool
     */
    protected function hasId() {
        $idName = self::getId();
        foreach ($idName as $id) {
            if (!isset($this->attributes[$id])) {
                return false;
            }
        }
        return true;
        
    }

    /**
     * 如果设置了id的值,就返回id值的键名数组
     * @return array
     * @throws ModelException
     */
    protected function getIdValues() {
        if ($this->hasId()) {
            $idName = self::getId();
            $ids = array();
            foreach ($idName as $id) {
                $ids[$id] = $this->attributes[$id];
            }
            return $ids;
        }

        throw new ModelException(get_called_class() . "没有标识id数据");
    }

    /**
     * 通过静态变量名称为$id， 获取对应数据库表或视图的标识名称。单一标识或集合标识(数组).
     * 非必须设置，如果设置则使用默认设置，详见 Model::DEFAULT_ID;
     * <pre>
     *      public static $id = 'primaryId';
     * </pre>
     * @return [type] [description]
     */
    protected static function getId() {
        if (isset(self::$id)) {
            return self::$id;
        }
        else {
            return self::DEFAULT_ID;
        }
    }

    /**
     * 魔术方法
     * @param $name
     * @param $value
     */
    public function __set($name, $value) {
        $this->attributes[$name] = $value;
    }

    /**
     * 魔术方法
     * @param $name
     * @return null
     */
    public function __get($name) {
        // 注意 当某个属性值为null时，isset($this->attribute[$name])会返回为空
        if (isset($this->attributes[$name])) {
            return $this->attributes[$name];
        }
        elseif (method_exists($this, $name)) {// 存在关联关系
            $relationObj = $this->{$name}();//调用函数
            Precondition::check(is_a($relationObj, RelationShip::class), "$name 方法必须返回值是RelationShip");
            Precondition::check(isset($this->attributes[$relationObj->localKey]), "键值必须存在");

            $relationObj->attribute = $name;//设置属性值

            if (RelationShip::ONE_TO_ONE == $relationObj->type) {// 1对1, 返回对应的Model类对象
                $m = Query::createSelect(self::$identifier)
                    ->from($relationObj->modelClass)
                    ->eq($relationObj->foreignKey, $this->attributes[$relationObj->localKey])
                    ->only();

                if (false == $m) {
                    $relationObj->modelObj = null;
                }
                else {
                    $relationObj->modelObj = $m;
                }
                
            }
            elseif (RelationShip::ONE_TO_MANY == $relationObj->type) {// 1对多,返回对应的ListModel类对象
                $relationObj->modelObj = new ListModel(
                    $relationObj->modelClass,
                    array($relationObj->foreignKey => $this->attributes[$relationObj->localKey])
                );
            }
           
            $this->attributes[$name] = $relationObj->modelObj;
            $this->relations->put($name, $relationObj);//FIXME 貌似没什么用
            return $this->attributes[$name];
        }
        return null;
    }

    /**
     * 对象转为json
     * @return string
     */
    public function toJson() {
        return json_encode($this->attributes);
    }

    /**
     * 判断属性是否存在
     * @param $name
     * @return bool
     */
    public function __isset($name) {
        if (isset($this->attributes[$name])) {
            return true;
        }
        return false;
    }

    /**
     * 删除属性值
     * @param $name
     */
    public function __unset($name) {
        unset($this->attributes[$name]);
    }

    /**
     * 输出为字符串
     * @return string
     */
    public function __tostring() {
        return 'model('. get_called_class() . ')' . $this->toJson();
    }

    /**
     * 当使用 print_r 或 var_dump 时的输出
     * @return array
     */
    public function __debuginfo() {
        return get_object_vars($this);
    }

    /**
     * 获取模型类对应的表或视图名称，必须设置，如未设置则抛出ModelException异常。
     * 示例：public static $table = 'tableName';
     *
     * @return string 对应的表或视图名称
     */
    protected static function getTable() {
        if (isset(static::$table)) {
            return static::$table;
        }
        throw new ModelException("类没有定义对应的数据库表或视图名称");
    }


}